from datetime import timedelta

from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone

from learning.models import Mistake, TeacherAnnouncement, TeacherAssignment


class UserModelTests(TestCase):
    def setUp(self):
        self.User = get_user_model()

    def test_student_can_be_assigned_to_teacher(self):
        teacher = self.User.objects.create_user(
            username="mr_lu",
            password="temp-pass",
            role=self.User.Role.TEACHER,
        )
        student = self.User.objects.create_user(
            username="alice",
            password="temp-pass",
            role=self.User.Role.STUDENT,
            assigned_teacher=teacher,
        )

        self.assertTrue(student.is_student)
        self.assertTrue(teacher.is_teacher)
        self.assertEqual(student.assigned_teacher, teacher)
        self.assertListEqual(list(teacher.students.all()), [student])

    def test_assigned_teacher_must_be_teacher(self):
        teacher = self.User.objects.create_user(
            username="mrs_huang",
            password="temp-pass",
            role=self.User.Role.TEACHER,
        )
        other_student = self.User.objects.create_user(
            username="bob",
            password="temp-pass",
            role=self.User.Role.STUDENT,
            assigned_teacher=teacher,
        )
        student = self.User(
            username="charlie",
            role=self.User.Role.STUDENT,
            assigned_teacher=other_student,
        )

        with self.assertRaises(ValidationError):
            student.full_clean()

    def test_students_related_manager_only_returns_students(self):
        teacher = self.User.objects.create_user(
            username="prof_xu",
            password="temp-pass",
            role=self.User.Role.TEACHER,
        )
        self.User.objects.create_user(
            username="dan",
            password="temp-pass",
            role=self.User.Role.STUDENT,
            assigned_teacher=teacher,
        )
        self.User.objects.create_user(
            username="emma",
            password="temp-pass",
            role=self.User.Role.STUDENT,
            assigned_teacher=teacher,
        )

        student_usernames = set(teacher.students.values_list("username", flat=True))
        self.assertSetEqual(student_usernames, {"dan", "emma"})


class AuthViewTests(TestCase):
    def setUp(self):
        self.User = get_user_model()
        self.teacher = self.User.objects.create_user(
            username="mentor",
            password="Temp-pass1",
            role=self.User.Role.TEACHER,
            first_name="Mentor",
        )
        self.student = self.User.objects.create_user(
            username="student1",
            password="Strongpass123",
            role=self.User.Role.STUDENT,
            assigned_teacher=self.teacher,
            first_name="Student",
            last_name="Lee",
            email="student@example.com",
        )
        self.unassigned_student = self.User.objects.create_user(
            username="free_student",
            password="Strongpass123",
            role=self.User.Role.STUDENT,
        )

    def test_register_page_renders_form(self):
        response = self.client.get(reverse("accounts:register"))

        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "accounts/register.html")
        self.assertContains(response, "创建学生账号")
        self.assertContains(response, reverse("accounts:login"))
        self.assertContains(response, 'name="teacher_username"')

    def test_student_can_register_with_teacher(self):
        response = self.client.post(
            reverse("accounts:register"),
            data={
                "username": "newstudent",
                "full_name": "Alice Zhang",
                "teacher_username": "mentor",
                "password1": "Strongpass123",
                "password2": "Strongpass123",
            },
            follow=True,
        )

        self.assertEqual(response.redirect_chain[-1][0], reverse("accounts:login"))
        new_user = self.User.objects.get(username="newstudent")
        self.assertTrue(new_user.is_student)
        self.assertEqual(new_user.assigned_teacher, self.teacher)

    def test_invalid_teacher_shows_error(self):
        response = self.client.post(
            reverse("accounts:register"),
            data={
                "username": "otherstudent",
                "full_name": "Bob Li",
                "teacher_username": "unknown",
                "password1": "Strongpass123",
                "password2": "Strongpass123",
            },
        )

        self.assertEqual(response.status_code, 200)
        form = response.context["form"]
        self.assertFormError(
            form, "teacher_username", "指定的老师不存在，请确认教师帐号。"
        )

    def test_login_page_renders_custom_template(self):
        response = self.client.get(reverse("accounts:login"))

        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "accounts/login.html")
        self.assertContains(response, "登录你的数学助手账号")
        self.assertContains(response, reverse("accounts:register"))

    def test_user_can_log_in(self):
        response = self.client.post(
            reverse("accounts:login"),
            data={"username": self.student.username, "password": "Strongpass123"},
        )

        self.assertRedirects(response, reverse("learning:mistake_list"))

    def test_profile_page_displays_user_details(self):
        self.client.login(username="student1", password="Strongpass123")

        response = self.client.get(reverse("accounts:profile"))

        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "accounts/profile.html")
        self.assertContains(response, "个人资料")
        self.assertContains(response, "student1")
        self.assertContains(response, "Student Lee")
        self.assertContains(response, "student@example.com")
        self.assertContains(response, "Mentor")

    def test_profile_requires_login(self):
        response = self.client.get(reverse("accounts:profile"))

        self.assertRedirects(
            response, f"{reverse('accounts:login')}?next={reverse('accounts:profile')}"
        )

    def test_logout_via_get_redirects(self):
        self.client.login(username="student1", password="Strongpass123")

        response = self.client.get(reverse("accounts:logout"))

        self.assertRedirects(response, reverse("accounts:login"))
        response = self.client.get(reverse("accounts:logout"))
        self.assertRedirects(response, reverse("accounts:login"))

    def test_teacher_profile_has_management_tools(self):
        self.client.login(username="mentor", password="Temp-pass1")

        response = self.client.get(reverse("accounts:profile"))

        self.assertTemplateUsed(response, "accounts/teacher_profile.html")
        self.assertContains(response, "教师工作台")
        self.assertContains(response, "关联学生")
        self.assertContains(response, "发布公告")
        self.assertContains(response, "发布作业")

    def test_teacher_can_link_unassigned_student(self):
        self.client.login(username="mentor", password="Temp-pass1")

        response = self.client.post(
            reverse("accounts:profile"),
            data={
                "action": "link_student",
                "student_username": self.unassigned_student.username,
            },
            follow=True,
        )

        self.unassigned_student.refresh_from_db()
        self.assertEqual(self.unassigned_student.assigned_teacher, self.teacher)
        self.assertRedirects(response, reverse("accounts:profile"))

    def test_teacher_linking_student_of_another_teacher_shows_error(self):
        other_teacher = self.User.objects.create_user(
            username="other_teacher",
            password="Temp-pass1",
            role=self.User.Role.TEACHER,
        )
        other_student = self.User.objects.create_user(
            username="locked_student",
            password="Strongpass123",
            role=self.User.Role.STUDENT,
            assigned_teacher=other_teacher,
        )
        self.client.login(username="mentor", password="Temp-pass1")

        response = self.client.post(
            reverse("accounts:profile"),
            data={
                "action": "link_student",
                "student_username": other_student.username,
            },
        )

        self.assertEqual(response.status_code, 200)
        other_student.refresh_from_db()
        self.assertEqual(other_student.assigned_teacher, other_teacher)
        self.assertContains(response, "该学生已由其他老师管理")

    def test_teacher_can_publish_announcement(self):
        self.client.login(username="mentor", password="Temp-pass1")

        response = self.client.post(
            reverse("accounts:profile"),
            data={
                "action": "create_announcement",
                "title": "期中考试安排",
                "content": "周五上午 9 点进行期中考试。",
            },
            follow=True,
        )

        self.assertRedirects(response, reverse("accounts:profile"))
        self.assertEqual(TeacherAnnouncement.objects.filter(teacher=self.teacher).count(), 1)
        announcement = TeacherAnnouncement.objects.get()
        self.assertEqual(announcement.title, "期中考试安排")

    def test_teacher_can_publish_assignment(self):
        due_date = (timezone.now() + timedelta(days=7)).date()
        self.client.login(username="mentor", password="Temp-pass1")

        response = self.client.post(
            reverse("accounts:profile"),
            data={
                "action": "create_assignment",
                "title": "函数图像练习",
                "description": "完成教材第 5 章习题 1-10 题。",
                "due_date": due_date.isoformat(),
            },
            follow=True,
        )

        self.assertRedirects(response, reverse("accounts:profile"))
        assignment = TeacherAssignment.objects.get()
        self.assertEqual(assignment.title, "函数图像练习")
        self.assertEqual(assignment.due_date, due_date)

    def test_teacher_can_edit_and_delete_announcement(self):
        announcement = TeacherAnnouncement.objects.create(
            teacher=self.teacher,
            title="旧公告",
            content="旧内容",
        )
        self.client.login(username="mentor", password="Temp-pass1")

        response = self.client.post(
            reverse("accounts:announcement_edit", args=[announcement.pk]),
            data={"title": "新公告", "content": "更新后的内容"},
            follow=True,
        )

        self.assertRedirects(response, reverse("accounts:profile"))
        announcement.refresh_from_db()
        self.assertEqual(announcement.title, "新公告")

        response = self.client.post(
            reverse("accounts:announcement_delete", args=[announcement.pk]),
            follow=True,
        )

        self.assertRedirects(response, reverse("accounts:profile"))
        self.assertFalse(
            TeacherAnnouncement.objects.filter(pk=announcement.pk).exists()
        )

    def test_teacher_can_edit_and_delete_assignment(self):
        assignment = TeacherAssignment.objects.create(
            teacher=self.teacher,
            title="旧作业",
            description="原描述",
            due_date=(timezone.now() + timedelta(days=3)).date(),
        )
        self.client.login(username="mentor", password="Temp-pass1")

        response = self.client.post(
            reverse("accounts:assignment_edit", args=[assignment.pk]),
            data={
                "title": "新作业",
                "description": "更新后的要求",
                "due_date": (timezone.now() + timedelta(days=5)).date().isoformat(),
            },
            follow=True,
        )

        self.assertRedirects(response, reverse("accounts:profile"))
        assignment.refresh_from_db()
        self.assertEqual(assignment.title, "新作业")

        response = self.client.post(
            reverse("accounts:assignment_delete", args=[assignment.pk]),
            follow=True,
        )
        self.assertRedirects(response, reverse("accounts:profile"))
        self.assertFalse(
            TeacherAssignment.objects.filter(pk=assignment.pk).exists()
        )

    def test_student_sees_announcements_and_assignments(self):
        TeacherAnnouncement.objects.create(
            teacher=self.teacher,
            title="考试安排",
            content="周五进行模拟考试。",
        )
        TeacherAssignment.objects.create(
            teacher=self.teacher,
            title="第六章练习",
            description="完成 1-10 题。",
        )
        self.client.login(username="student1", password="Strongpass123")

        response = self.client.get(reverse("accounts:profile"))

        self.assertContains(response, "考试安排")
        self.assertContains(response, "第六章练习")
        self.assertContains(response, "公告通知")
        self.assertContains(response, "作业列表")

    def test_teacher_can_resolve_mistake_from_dashboard(self):
        mistake = Mistake.objects.create(
            student=self.student,
            title="函数题",
            question="f(x) = x^2 - 1",
            status=Mistake.Status.SUBMITTED,
        )
        self.client.login(username="mentor", password="Temp-pass1")

        response = self.client.post(
            reverse("accounts:profile"),
            data={
                "action": "resolve_mistake",
                "mistake_id": mistake.pk,
                "feedback": "回去复习导数。",
            },
            follow=True,
        )

        self.assertRedirects(response, reverse("accounts:profile"))
        mistake.refresh_from_db()
        self.assertEqual(mistake.status, Mistake.Status.RESOLVED)
        self.assertEqual(mistake.teacher_feedback, "回去复习导数。")
        self.assertTrue(self.student.notifications.filter(title__contains="错题已批阅").exists())

    def test_student_can_submit_assignment(self):
        assignment = TeacherAssignment.objects.create(
            teacher=self.teacher,
            title="代数练习",
            description="完成 1-5 题。",
            due_date=(timezone.now() + timedelta(days=3)).date(),
        )
        self.client.login(username="student1", password="Strongpass123")

        response = self.client.post(
            reverse("accounts:assignment_submit", args=[assignment.pk]),
            data={"content": "已经完成。"},
            follow=True,
        )

        self.assertRedirects(response, reverse("accounts:mailbox"))
        submission = assignment.submissions.get(student=self.student)
        self.assertEqual(submission.content, "已经完成。")
        self.assertTrue(
            self.teacher.notifications.filter(title__contains="作业已提交").exists()
        )

    def test_mailbox_lists_notifications_and_marks_read(self):
        notification = self.student.notifications.create(
            title="测试通知",
            message="内容",
            link=reverse("accounts:profile"),
        )
        self.client.login(username="student1", password="Strongpass123")

        response = self.client.get(reverse("accounts:mailbox"))

        self.assertContains(response, "测试通知")
        notification.refresh_from_db()
        self.assertIsNotNone(notification.read_at)

    def test_other_teacher_cannot_edit_or_delete_resources(self):
        other_teacher = self.User.objects.create_user(
            username="other_teacher",
            password="Temp-pass1",
            role=self.User.Role.TEACHER,
        )
        announcement = TeacherAnnouncement.objects.create(
            teacher=self.teacher, title="公告", content="内容"
        )
        assignment = TeacherAssignment.objects.create(
            teacher=self.teacher, title="作业", description="说明"
        )
        self.client.login(username="other_teacher", password="Temp-pass1")

        response = self.client.post(
            reverse("accounts:announcement_edit", args=[announcement.pk]),
            data={"title": "x", "content": "y"},
        )
        self.assertEqual(response.status_code, 404)

        response = self.client.post(
            reverse("accounts:announcement_delete", args=[announcement.pk])
        )
        self.assertEqual(response.status_code, 404)

        response = self.client.post(
            reverse("accounts:assignment_edit", args=[assignment.pk]),
            data={"title": "y", "description": "z"},
        )
        self.assertEqual(response.status_code, 404)

        response = self.client.post(
            reverse("accounts:assignment_delete", args=[assignment.pk])
        )
        self.assertEqual(response.status_code, 404)

    def test_student_cannot_access_teacher_actions(self):
        self.client.login(username="student1", password="Strongpass123")

        response = self.client.post(
            reverse("accounts:profile"),
            data={
                "action": "link_student",
                "student_username": "free_student",
            },
        )

        self.assertEqual(response.status_code, 403)
